package images

import (
	"image"
	"log"
)

const (
	_ratio = 5
	_size  = 3
)

// Match 匹配.
type Match struct {
	Ratio          int
	Distance       int
	Size           int
	FuzzySize      int
	target         image.Image
	template       image.Image
	windowWidth    int
	windowHeight   int
	templateWidth  int
	templateHeight int
	stepWidth      int
	stepHeight     int
}

// NewMatch 新建匹配.
func NewMatch(target image.Image, template image.Image) (*Match, error) {
	w := target.Bounds().Dx() - template.Bounds().Dy()
	h := target.Bounds().Dy() - template.Bounds().Dy()

	if w < 0 || h < 0 {
		return nil, ErrorSize
	}

	return &Match{
		Ratio:          _ratio,
		Distance:       0,
		Size:           _size,
		FuzzySize:      _size,
		target:         target,
		template:       template,
		windowWidth:    w,
		windowHeight:   h,
		templateWidth:  template.Bounds().Dx(),
		templateHeight: template.Bounds().Dy(),
	}, nil
}

// NewFastMatch 快速匹配.
func NewFastMatch(target image.Image, template image.Image) (*Match, error) {
	return NewMatch(ToGray(target), ToGray(template))
}

// Match 匹配.
func (m *Match) Match() ([]*Dot, error) {
	ret, dots := Dots{}, DotSet{}

	for _, r := range m.fuzzy().Top(m.Size*m.FuzzySize, m.Distance) {
		minX, minY, maxX, maxY := m.box(r)
		// 精确查找
		for y := minY; y < maxY; y++ {
			for x := minX; x < maxX; x++ {
				if dots.Has(x, y) {
					continue
				}

				if dot, err := m.dot(x, y); err == nil {
					ret = append(ret, dot)

					dots.Add(x, y)
				}
			}
		}
	}

	if len(ret) > 0 {
		return ret.Match(m.Size, m.Distance), nil
	}

	return nil, ErrorNotFound
}

// fuzzy 模糊查找.
func (m *Match) fuzzy() Dots {
	top := Dots{}
	m.stepWidth, m.stepHeight = m.templateWidth/m.Ratio, m.templateHeight/m.Ratio

	if m.stepWidth < 1 {
		m.stepWidth = 1
	}

	if m.stepHeight < 1 {
		m.stepHeight = 1
	}

	for y := 0; y < m.windowHeight; y += m.stepHeight {
		for x := 0; x < m.windowWidth; x += m.stepWidth {
			if dot, err := m.dot(x, y); err == nil {
				top = append(top, dot)
			} else {
				log.Println(err)
			}
		}
	}

	return top
}

func (m *Match) dot(x, y int) (*Dot, error) {
	dot := &Dot{X: x, Y: y}

	sub, err := SubImage(m.target, image.Rect(x, y, x+m.templateWidth, y+m.templateHeight))
	if err != nil {
		return dot, err
	}

	dot.Distance, err = Distance(sub, m.template)

	return dot, err
}

func (m *Match) box(r *Dot) (minX, minY, maxX, maxY int) {
	minY, minX = r.Y-m.stepHeight, r.X-m.stepWidth
	maxY, maxX = r.Y+m.stepHeight, r.X+m.stepHeight

	if minY < 0 {
		minY = 0
	}

	if minX < 0 {
		minX = 0
	}

	if maxY > m.windowHeight {
		maxY = m.windowHeight
	}

	if maxX > m.windowWidth {
		maxX = m.windowWidth
	}

	return
}
